#include <config.h>
#include "geoip.h"
#include <stdio.h>
#include <unistd.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
//#include <iconv.h>
#include <glib.h>
#include <mysql.h>
#include <getopt.h>
#include <stdio.h>

#include <string>
#include <map>
#include <new>
#include <iostream>

#include <stack>
#include <map>
#include <iostream>

#include <pthread.h>

#include "db.h"
#include "GBConfig.h"
#include "geoip.h"
#include "server.h"
#include "sax.h"
#include <mysql.h>
#include <assert.h>

#include <HL2MasterServer.h>

// http://www.xmlsoft.org/examples/parse4.c

struct masterServer {
	const char *masterAddr;
	const char *game;
};

struct masterServer masterList[] = {
	{
		"hl2master.steampowered.com:27011",
		"hl2"
	},
	{
		"hl1master.steampowered.com:27010",
		"hl"
	}
};

struct update_thread_arg {
	pthread_t tid;
	pthread_mutex_t mutex;
	stack<ServerInfo *> *servers;
	char game[16];
};

using namespace std;

MYSQL conn;

int concurrency = 1;
stack<ServerInfo *> *servers;

char *coerceToUtf8(const char *p);
int update_locations();
int update_avarages();
int clean_old_data();
int update_server_directly();

int main(int argc, char **argv)
{
	int maxsim = 1000;
	int c;
	const char *cfgfile = "/etc/gamebrowser.cfg";
	std::string dbh, dbn, dbu, dbp;
#ifdef USE_QSTAT
	unsigned int i=0;
#endif

	while((c = getopt(argc, argv, "m:hc:"))>0) {
		switch(c) {
			case 'c':
				cfgfile = optarg;
				break;
			case 'm':
				maxsim = atoi(optarg);
				break;
			case 'h':
			case '?':
			case ':':
			default:
				fprintf(stderr, "%s -m 60 -c /etc/gamebrowser.cfg\n", argv[0]);
				exit(1);
		}
	}

	if (!g_thread_supported ()) g_thread_init (NULL);

	GBConfig cfg(cfgfile);
	dbh = cfg.getValue("dbhost");
	dbn = cfg.getValue("dbname");
	dbu = cfg.getValue("dbuser");
	dbp = cfg.getValue("dbpass");

	concurrency = cfg.getIntValue("concurrency");

	db_setConfig(dbh.c_str(), dbu.c_str(), dbp.c_str(), dbn.c_str());

#ifdef USE_QSTAT
	db_connect();
	for(i=0;i<sizeof(masterList)/sizeof(struct masterServer);i++) { 
		FILE *p;
		char cmd[1024];
		snprintf(cmd, 1024, "quakestat -progress -interval 0.2 -sendinterval 2 -u -R -maxsim %i -xml -utf8 -hla2sm %s", maxsim, masterList[i].masterAddr);
		//snprintf(cmd, 1024, "quakestat -progress -u -R -xml -utf8 -hla2sm %s", masterList[i].masterAddr);
		//snprintf(cmd, 1024, "quakestat -u -maxsim %i -xml -utf8 -hla2sm %s", maxsim, masterList[i].masterAddr);
#ifndef LIVE
		cout << cmd << endl;
		p = popen(cmd, "r");
#else
		p = fopen("withrules.xml", "r");
#endif
		if(p) {
			sax_parse_xmlfile(p, masterList[i].game);
			fclose(p);
		} else {
			perror("popen");
			exit(1);
		}
	}
#else
	update_server_directly();
#endif

	db_connect(&conn);
	update_avarages();
	update_locations();
	clean_old_data();
	db_close(&conn);

	exit(0);
}

pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;

void *update_thread(void *arg)
{
	struct update_thread_arg *a = (struct update_thread_arg *)arg;
	MYSQL c;
	int r;

	a->tid = pthread_self();

	r = db_connect(&c);
	if(r<0) {
		fprintf(stderr, "%li:%s: error connecting on mysql...\n", a->tid, __func__);
		pthread_exit(NULL);
	}

	while(1) {
		fprintf(stderr, "%li:%s: locking...\n", a->tid, __func__);
		if(!pthread_mutex_lock(&list_mutex)) {
			fprintf(stderr, "%li:%s: locked...\n", a->tid, __func__);
			if(!servers->empty()) {
				ServerInfo *server = servers->top();
				servers->pop();
				fprintf(stderr, "%li:%s: unlocked...\n", a->tid, __func__);
				pthread_mutex_unlock(&list_mutex);

				map<string, string> info;
				const struct sockaddr_in *addr = server->getAddress();
				std::string str = inet_ntoa(addr->sin_addr);
				struct server s;

				s.port = ntohs(addr->sin_port);

				if(server->update()) {
					info = server->getAttributes();
					cout << info["name"] << " " << info["numplayers"] << "/" << info["maxplayers"] << endl;
				} else {
					cout << "* timeout *" << endl;
					delete(server);
					continue;
				}

				strncpy(s.ip, str.c_str(), 16);

				info = server->getAttributes();

				if(info.find("name") != info.end()) {
					str = info["name"];
					strncpy(s.name, str.c_str(), 256);
				}
				if(info.find("gametype") != info.end()) {
					str = info["gametype"]; // hardcoded?
					strncpy(s.gametype, str.c_str(), 256);
				}
				if(info.find("gamedescription") != info.end()) {
					str = info["gamedescription"];
					strncpy(s.gamedescription, str.c_str(), 256);
				}

				if(info.find("maxplayers") != info.end()) {
					str = info["maxplayers"];
					s.maxplayers = atoi(str.c_str());
				}
				if(info.find("numplayers") != info.end()) {
					str = info["numplayers"];
					s.numplayers = atoi(str.c_str());
				}

				if(info.find("dedicated") != info.end()) {
					str = info["dedicated"];
					s.dedicated = atoi(str.c_str());
				}

				if(info.find("secure") != info.end()) {
					str = info["secure"];
					s.secure = atoi(str.c_str());
				}

				if(info.find("os") != info.end()) {
					str = info["os"];
					strncpy(s.os, str.c_str(), 32);
				}

				strncpy(s.game, a->game, 16);

				/*
				   if(server->updateRules()) {
				   info = server->getAttributes();
				   cout << "\tos: " << info["os"] << endl;
				   cout << "\tbots: " << info["bots"] << endl;

				   }

				   info = server->getRules();*/

				/*	char ip[16];
					int port;
					char name[256];
					char gametype[256];
					char game[16];
					char os[32];
					int maxplayers;
					int numplayers;
					int dedicated;
					int secure;
					int bots;*/

				update_server(&c, &s);
				delete(server);
			} else {
				fprintf(stderr, "%li:%s: unlocked (done)...\n", a->tid, __func__);
				pthread_mutex_unlock(&list_mutex);
				//pthread_cond_signal(&a->done);
				break;
			}
		}
	}

	db_close(&c);
	pthread_exit(NULL);
}

int update_server_directly()
{
	unsigned int i;
	struct update_thread_arg *updaters = (struct update_thread_arg *)g_malloc0(sizeof(pthread_t) * concurrency);
	enum STEAM_REGION regions[] = {
		STEAM_REGION_SA,
		STEAM_REGION_US_EC,
		STEAM_REGION_US_WC,
		STEAM_REGION_EU,
		STEAM_REGION_AS,
		STEAM_REGION_AUS,
		STEAM_REGION_ME,
		STEAM_REGION_AF,
		STEAM_REGION_OTHER
	};

	for(i=0;i<sizeof(masterList)/sizeof(struct masterServer);i++) { 
		char *addr = strdup(masterList[i].masterAddr);
		int port = 27010;
		char *tmp = NULL;
		int z;
		if((tmp = strchr(addr, ':'))) {
			*tmp = '\0';
			tmp++;
			port = atoi(tmp);
		}
		HL2MasterServer *ms = new HL2MasterServer(addr, port);
		for(z=0;;z++) {
			int k;
			cout << "Getting server list" << endl;
			//stack<ServerInfo *> &servers = ms->list(regions[z]);
			//ms->clearList();
			servers = ms->list(regions[z]);
			cout << "Getting servers info" << endl;
			for(k=0;k<concurrency;k++) {
				fprintf(stderr, "server: %p", &servers);
				updaters[k].servers = servers;
				fprintf(stderr, " update servers: %p\n", &updaters[k].servers);
				strncpy(updaters[k].game, masterList[i].game, 16);
				//pthread_cond_init(&updaters[k].done, NULL);
				pthread_mutex_init(&updaters[k].mutex, NULL);
				pthread_create(&(updaters[k].tid), NULL, update_thread, &(updaters[k]));
			}
			for(k=0;k<concurrency;k++) {
				if(!pthread_mutex_lock(&updaters[k].mutex)) {
					int *ret;
					cout << updaters[k].tid << ":" << k <<  ": aguardando" << endl;
					pthread_join(updaters[k].tid, (void **)&ret);
					cout << updaters[k].tid << ":" << k <<  ": finalizado..." << endl;
				}
			}

#if 0
			while(!servers.empty()) {
				ServerInfo *server = servers.top();
				map<string, string> info;
				servers.pop();
				const struct sockaddr_in *addr = server->getAddress();
				std::string str = inet_ntoa(addr->sin_addr);
				struct server s;

				s.port = ntohs(addr->sin_port);

				if(server->update()) {
					info = server->getAttributes();
					cout << info["name"] << " " << info["numplayers"] << "/" << info["maxplayers"] << endl;
				} else {
					cout << "* timeout *" << endl;
					continue;
				}

				strncpy(s.ip, str.c_str(), 16);

				info = server->getAttributes();

				if(info.find("name") != info.end()) {
					str = info["name"];
					strncpy(s.name, str.c_str(), 256);
				}
				if(info.find("gametype") != info.end()) {
					str = info["gametype"]; // hardcoded?
					strncpy(s.gametype, str.c_str(), 256);
				}
				if(info.find("gamedescription") != info.end()) {
					str = info["gamedescription"];
					strncpy(s.gamedescription, str.c_str(), 256);
				}

				if(info.find("maxplayers") != info.end()) {
					str = info["maxplayers"];
					s.maxplayers = atoi(str.c_str());
				}
				if(info.find("numplayers") != info.end()) {
					str = info["numplayers"];
					s.numplayers = atoi(str.c_str());
				}

				if(info.find("dedicated") != info.end()) {
					str = info["dedicated"];
					s.dedicated = atoi(str.c_str());
				}

				if(info.find("secure") != info.end()) {
					str = info["secure"];
					s.secure = atoi(str.c_str());
				}

				if(info.find("os") != info.end()) {
					str = info["os"];
					strncpy(s.os, str.c_str(), 32);
				}

				strncpy(s.game, masterList[i].game, 16);

				/*
				   if(server->updateRules()) {
				   info = server->getAttributes();
				   cout << "\tos: " << info["os"] << endl;
				   cout << "\tbots: " << info["bots"] << endl;

				   }

				   info = server->getRules();*/

				/*	char ip[16];
					int port;
					char name[256];
					char gametype[256];
					char game[16];
					char os[32];
					int maxplayers;
					int numplayers;
					int dedicated;
					int secure;
					int bots;*/

				update_server(&s);
				delete(server);
			}
#endif
			if(regions[z] == STEAM_REGION_OTHER) {
				break;
			}
		}
		delete(ms);

		free(addr);
	}
	return 0;
}

int clean_old_data()
{
	MYSQL_RES *sth;
	sth = db_doSql(&conn, "DELETE FROM server "
		"WHERE (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(lastUpdate)) >= 60 * 60 * 6"
		//"WHERE (TO_DAYS(NOW()) - TO_DAYS(lastupdate))>=1"
	);
	if(sth) {
		mysql_free_result(sth);
	}
	sth = db_doSql(&conn, "CREATE TEMPORARY TABLE TMP SELECT location.ip FROM location LEFT JOIN server USING (ip) WHERE server.ip IS NULL");
	if(sth) {
		mysql_free_result(sth);
	}
	sth = db_doSql(&conn, "DELETE FROM location WHERE ip IN (SELECT ip FROM TMP)");
	if(sth) {
		mysql_free_result(sth);
	}
	return 0;
}

int update_locations()
{
	MYSQL_RES *sth = db_doSql(&conn, "SELECT DISTINCT(ip) FROM server LEFT JOIN location USING (ip) WHERE location IS NULL");
	MYSQL_RES *sth2 = NULL;
	MYSQL_ROW row;
	int ret = 0;
	init_geoip();
	while((row = mysql_fetch_row(sth))) {
		char sql[1024];
		float lat=0, lon=0;
		getLatLongByIp(row[0], &lat, &lon);
		snprintf(sql, 1024, "INSERT INTO location (ip, location) VALUES ('%s', GeomFromText('POINT(%0.10f %0.10f)'))", row[0], lat, lon);
		sth2 = db_doSql(&conn, sql);
		if(sth2)
			mysql_free_result(sth2);
		ret++;
	}
	mysql_free_result(sth);
	close_geoip();
	return ret;
}

char *coerceToUtf8(const char *p)
{
	char *end;
	char *ret;
	int i;
	GString *coerced = g_string_new(NULL);

	while ( ! g_utf8_validate( p, -1, (const char **)&end ) ) {
		char buf[2]="";
		char *out;
		gsize r, w;
		GError *error;

		error = NULL;
		buf[0] =  0xff & *end;
		out = g_locale_to_utf8(buf, 1, &r, &w, &error);

		if(p != NULL) {
			g_string_append_len(coerced, p, end - p);
		}
		if(out != NULL) {
			g_string_append_len(coerced, out, w);
		}

		if(error) {
			g_error_free(error);
		}
		g_free(out);
		p = (char*)end;
		p++;
	}
	g_string_append(coerced, p);
	ret = g_string_free(coerced, FALSE);
	for(i=0;ret[i];i++) {
		//if(!isprint(ret[i])) {
		if(ret[i]<' ') {
			ret[i] = ' ';
		}
	}
	return ret;
}

int update_avarages()
{
	MYSQL_RES *sth = db_doSql(&conn, "UPDATE server SET avarage = totalplayers / updates WHERE updates > 1");
	if(sth) {
		mysql_free_result(sth);
	}
	return 1;
}
